/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file request.c
 * Request and GET/POST params parser
 */

#include <httpd.h>
#include <util_filter.h>
#include <http_log.h>
#include <http_protocol.h>

#include <apr_strings.h>
#include <apr_tables.h>

#include "request.h"
#include "logging.h"
#include "pyhp_layer.h"
#include "upload.h"

/**
 * Parse key=value pairs from the given url and places them inside
 * a python dictionary
 * @param data The part of the url to parse (after ?...)
 * @param dict The dictionary where to put the found data
 */
static void url_parse_params(char *data, PyObject *dict)
{
    char *s;
    char *key, *val, *eq;
    PyObject *str;

    /* Translate "+" chars in " " */
    s = data;
    while(s[0]) {
        if(s[0] == '+') s[0] = ' ';
        s++;
    }

    s = strtok(data, "&");

    do {
        if( (s) && (strlen(s)) ) {
            if(!(eq = strchr(s,'='))) {
                // FIXME: malformed URI
                continue;
            }

            *eq = 0;
            key = s;
            val = eq + 1;

            if(strlen(key) == 0)
            {
                // FIXME: malformed URI
                continue;
            }

            if(!strlen(val)) val = "";

            ap_unescape_url(key);
            ap_unescape_url(val);

            str = PyString_FromString(val);
            PyDict_SetItemString(dict, key, str);
            Py_XDECREF(str);
        }
    } while ((s = strtok(NULL, "&")));
}

/**
 * Parses The GET arguments of a request putting them inside a python dictionary
 * @param r The request for which to parse GET arguments
 * @param dict The dictionary where to put the parsed args
 */
static void request_parse_get_params(request_rec *r, PyObject *dict)
{
    char *s;

    if(!r->args)
        return;

    s = strdup(r->args);
    url_parse_params(s, dict);
    free(s);
}

/**
 * Parses The POST arguments of a request putting them inside a python dictionary
 * @param r The request for which to parse POST arguments
 * @param dict The dictionary where to put the parsed args
 */
static void request_parse_post_params(request_rec *r,
                                      PyObject *post, PyObject *upload)
{
    apr_bucket_brigade *bb;
    apr_bucket *b;
    apr_status_t rv;
    conn_rec *c = r->connection;

    char *request;// = "";
    long request_len = 0;
    long expected_len = 0;

    char tmp_reqbuf[HUGE_STRING_LEN];
    long last_read_len = 0;

    char *post_type = (char *)apr_table_get(r->headers_in, "Content-Type");
    char *v = (char *)apr_table_get(r->headers_in, "Content-Length");

    if (v)
        expected_len = strtol(v, &v, 10);
    else {
        pyhp_log(r, PyHP_ERR, "Impossible to get content length of the request");
        return;
    }

    if (ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK) != OK) {
        pyhp_log(r, PyHP_ERR, "Error setting up request entity data");
        return;
    }

    while (!ap_should_client_block(r)) {
        pyhp_log(r, PyHP_ERR, "No data inside request");
        return;
    }

    if (expected_len == LONG_MAX) {
        pyhp_log(r, PyHP_ERR, "Uhu? Request too large");
        return;
    }
    request = apr_palloc(r->pool, expected_len + 1);

    while ((last_read_len = ap_get_client_block(r, tmp_reqbuf, HUGE_STRING_LEN)) > 0) {
        memcpy(request+request_len, tmp_reqbuf, last_read_len);
        request_len += last_read_len;
    }

    if (expected_len != request_len)
        pyhp_logf(r, PyHP_WARN, "Expected request of size %d, got %d", expected_len, request_len);

    if (post_type) {
        pyhp_logf(r, PyHP_DEBUG, "%s", post_type);
        if (strcasestr(post_type, "application/x-www-form-urlencoded") == post_type ) {
            request[request_len] = '\0';
            url_parse_params(request, post);
        }
        else if (strcasestr(post_type, "multipart/form-data") == post_type)
            pyhp_multipart_form_parse(r, request, request_len, post, upload);
        else
            pyhp_logf(r, PyHP_ERR, "Unknown form encoding format: %s", post_type);
    }
}

/**
 * Parses Request GET/POST parameters exposing them to the python layer
 * @param r The request for which to parse the parameters
 * @param pydata The Python layer where to create the GET and POST dictionaries
 */
void pyhp_request_parse_params(PyHPData *pydata, request_rec *r)
{
    PyObject *get = PyDict_New();
    PyObject *post = PyDict_New();
    PyObject *uploads = PyDict_New();

    pyhp_layer_set_object(pydata, "get", get);
    request_parse_get_params(r, get);

    pyhp_layer_set_object(pydata, "post", post);
    pyhp_layer_set_object(pydata, "uploads", uploads);
    if (r->method_number == M_POST)
        request_parse_post_params(r, post, uploads);
}

static int parse_request_callback(PyObject *dict, char const *key,
                                  char const *val)
{
    PyObject *str = PyString_FromString(val);
    PyObject *siblings = PyDict_GetItemString(dict, key);

    if (!siblings) {
        siblings = PyList_New(0);
        PyDict_SetItemString(dict, key, siblings);
        Py_XDECREF(siblings);
    }

    PyList_Append(siblings, str);
    Py_XDECREF(str);

    return 1;
}

/**
 * Parses Request Headers field exposing them to the python layer as a dict
 * @param pydata The python layer where to create the headers dictionary
 * @param r The request for which to parse the headers
 */
void pyhp_request_parse_headers(PyHPData *pydata, request_rec *r)
{
    PyObject *d = PyDict_New();
    apr_table_do((apr_table_do_callback_fn_t*)parse_request_callback, d,
                 r->headers_in, NULL);
    pyhp_layer_set_object(pydata, "headers_in", d);

    d = PyDict_New();
    // headers_out is usually empty?
    apr_table_do((apr_table_do_callback_fn_t*)parse_request_callback, d,
                 r->headers_out, NULL);
    pyhp_layer_set_object(pydata, "headers_out", d);


    //Export some utilities to pyhp.env dictionary
    d = PyDict_New();

    pyhp_pydict_add_string(d, "HOSTNAME", r->hostname);
    pyhp_pydict_add_string(d, "AUTH_NAME", r->user);
    pyhp_pydict_add_string(d, "AUTH_TYPE", r->ap_auth_type);
    pyhp_pydict_add_string(d, "REQUEST_URI", (r->prev ? r->prev->unparsed_uri : r->unparsed_uri));
    pyhp_pydict_add_string(d, "URI", r->uri);
    pyhp_pydict_add_string(d, "FILENAME", r->filename);
    pyhp_pydict_add_string(d, "CANONICAL_FILENAME", r->canonical_filename);
    pyhp_pydict_add_string(d, "QUERY_ARGS", r->args);

    pyhp_layer_set_object(pydata, "env", d);
}

/**
 * Writes output headers and cookies
 */
void pyhp_request_write_headers(PyHPData * pydata, request_rec *r)
{
    PyObject *key, *value;
    Py_ssize_t pos = 0;
    PyObject *dict = pyhp_layer_get_object(pydata, "headers_out");

    //Write the headers
    while(PyDict_Next(dict, &pos, &key, &value)) {
        PyObject *siblings = value;
        if (PyString_Check(siblings)) {
            apr_table_add(r->headers_out, PyString_AsString(key),
                                           PyString_AsString(value));
        }
        else if (PyList_Check(siblings)) {
            Py_ssize_t cur;
            Py_ssize_t len = PyList_GET_SIZE(siblings);
            for (cur = 0; cur < len; ++cur) {
                value = PyList_GET_ITEM(siblings, cur);
                apr_table_add(r->headers_out, PyString_AsString(key),
                                               PyString_AsString(value));
            }
        }
    }
}
